feat: 에러 핸들링 및 공통 UI 처리#118
Conversation
- getSeedsStats, getBookDetail, getReadingRecords를 async로 병렬 호출 - 응답 대기 후 하나라도 실패 시 UiState.Error로 전환
Walkthrough에러 핸들링 및 UI 처리를 대대적으로 개선하는 작업이 이루어졌습니다. 에러 상태를 단순 메시지에서 예외 객체로 일원화하고, 에러 UI 컴포넌트( Changes
Sequence Diagram(s)sequenceDiagram
participant Presenter
participant ErrorEventHelper
participant MainActivity
participant ReedDialog
Presenter->>ErrorEventHelper: postErrorDialog(scope, exception, action)
ErrorEventHelper->>MainActivity: emit ErrorEvent.ShowDialog(spec)
MainActivity->>ReedDialog: show dialog with spec
ReedDialog->>MainActivity: on confirm, invoke action
sequenceDiagram
participant Presenter
participant UI
Presenter->>UI: emit UiState.Error(exception)
UI->>ReedErrorUi: show error UI with exception, onRetryClick
ReedErrorUi->>Presenter: onRetryClick event
Presenter->>UI: retry logic, update state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 변경사항 없음) Suggested reviewers
Poem
Note 🔌 MCP (Model Context Protocol) integration is now available in Early Access!Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 13
🔭 Outside diff range comments (2)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt (2)
92-100: 감정 리소스 매핑 함수 public 전환: 안정성 주석 및 타입 힌트 추가 권장
- displayName(표시 문자열)에 의존하면 로케일/카피 변경 시 매핑이 깨질 수 있습니다. 가능하면 안정적인 코드/enum(예: 서버 코드나 core:model의 EmotionModel 등)에 기반한 매핑으로 전환을 권장합니다.
- 리소스 ID 반환에는 @DrawableRes 애노테이션을 부여해 호출 측 타입 안정성을 높여주세요.
예시:
+import androidx.annotation.DrawableRes - fun getEmotionImageResourceByDisplayName(displayName: String): Int { + @DrawableRes + fun getEmotionImageResourceByDisplayName(displayName: String): Int { return when (displayName) { "따뜻함" -> R.drawable.img_warm "즐거움" -> R.drawable.img_joy "슬픔" -> R.drawable.img_sad "깨달음" -> R.drawable.img_insight else -> R.drawable.img_warm } }추가 제안(선택): displayName 기반 함수는 유지하되, 내부적으로 안정 키→리소스 매핑을 우선하고, displayName→안정 키 변환 계층을 분리해두면 i18n 변화에 견고해집니다.
62-69: 빈 emotionTags 인덱싱으로 인한 충돌 가능성 (IndexOutOfBoundsException) 방지 필요Preview에서
emotionTags = persistentListOf()로 비어있는 리스트를 넣고 있고, 런타임에서도 비어있을 수 있습니다. 현재emotionTags[0]접근은 즉시 크래시를 유발합니다.안전 접근으로 변경 제안:
- Image( - painter = painterResource(getEmotionImageResourceByDisplayName(emotionTags[0])), - contentDescription = "Emotion Graphic", - modifier = Modifier - .size(40.dp) - .clip(CircleShape), - ) + val primaryEmotion = emotionTags.firstOrNull() + if (primaryEmotion != null) { + Image( + painter = painterResource(getEmotionImageResourceByDisplayName(primaryEmotion)), + contentDescription = stringResource(R.string.cd_emotion_graphic), + modifier = Modifier + .size(40.dp) + .clip(CircleShape), + ) + }태그 텍스트도 동일하게 보호:
- Text( - text = "#${emotionTags[0]}", - ... - ) + if (primaryEmotion != null) { + Text( + text = "#$primaryEmotion", + ... + ) + }참고:
R.string.cd_emotion_graphic는 strings.xml에 추가 필요합니다(아래 i18n 코멘트 참고).Also applies to: 70-76
🧹 Nitpick comments (18)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/ErrorScope.kt (1)
3-5: KDoc 보강 제안 (의도와 사용처 명시)간단한 주석만으로도 가독성과 일관성이 높아집니다. 아래처럼 추가를 고려해주세요.
package com.ninecraft.booket.core.common.constants +/** + * 에러가 발생/표시되는 영역을 구분합니다. + * - GLOBAL: 앱 전역(메인 등)에서의 에러 + * - LOGIN: 로그인/세션 관련 에러 + * - BOOK_REGISTER: 책 등록 플로우 관련 에러 + * - RECORD_REGISTER: 기록 등록 플로우 관련 에러 + */ enum class ErrorScope { - GLOBAL, LOGIN, BOOK_REGISTER, RECORD_REGISTER + GLOBAL, LOGIN, BOOK_REGISTER, RECORD_REGISTER }core/ui/src/main/res/values/strings.xml (1)
5-6: 문구/띄어쓰기 미세 개선 제안 (선택사항)사내 문체 가이드에 따라 아래 형태도 고려해 보세요.
-<string name="network_error_message">네트워크 연결이 불안정합니다.\n인터넷 연결을 확인해주세요</string> -<string name="server_error_message">알 수 없는 문제가 발생했어요.\n다시 시도해주세요</string> +<string name="network_error_message">네트워크 연결이 불안정합니다.\n인터넷 연결을 확인해 주세요</string> +<string name="server_error_message">알 수 없는 문제가 발생했어요.\n잠시 후 다시 시도해 주세요</string>또한, 다이얼로그/풀스크린 UI에서 줄바꿈 적용이 일관적으로 보이는지 실제 기기에서 한 번만 더 확인해 주세요.
feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUiState.kt (1)
12-17: @immutable 주석 추가 고려Compose 재구성 비용 감소 및 분석기 힌트를 위해 UiState에도 @immutable 적용을 권장합니다.
sealed interface UiState { + @Immutable + // 또는 sealed interface 자체에 @Immutable 부여 data object Idle : UiState data object Loading : UiState data object Success : UiState data class Error(val exception: Throwable) : UiState }참고: sealed interface 자체에 @immutable를 붙이는 방식도 가능합니다(팀 컨벤션에 맞춰 선택).
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt (1)
13-18: @immutable 적용으로 Compose 친화성 강화Home과 동일하게 UiState에도 @immutable 부여를 권장합니다.
sealed interface UiState { + @Immutable data object Idle : UiState data object Loading : UiState data object Success : UiState data class Error(val exception: Throwable) : UiState }feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt (1)
64-64: contentDescription 하드코딩(i18n) → stringResource 사용 권장접근성 문자열은 현지화 리소스로 관리해야 합니다.
예시:
- contentDescription = "Emotion Graphic", + contentDescription = stringResource(R.string.cd_emotion_graphic),필요 시
feature/detail모듈의res/values/strings.xml에cd_emotion_graphic키를 추가해 주세요. 매개값(예: 감정명)을 포함시키려면 포맷 문자열(%1$s)로 정의 후stringResource(R.string.cd_emotion_graphic, primaryEmotion)형태로 사용 가능합니다.feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.kt (1)
24-24: 교차 컴포넌트 의존 최소화를 위한 유틸 분리 제안
getEmotionImageResourceByDisplayName가 ‘book/component/RecordItem.kt’에 위치한 채 ‘record/component’에서 참조되고 있습니다. detail 기능 전반에서 사용하는 매핑이므로feature/detail/.../common/EmotionResources.kt같은 파일로 추출해 두면 모듈(패키지) 간 결합이 낮아지고 재사용/테스트가 수월해집니다.feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt (1)
90-97: 에러 표현 형식 통일 제안
UiState에는Throwable을 그대로 넘기는데, 같은 지점의footerState는 문자열만 넘기고 있습니다.
두 컴포넌트가 다른 타입을 요구하더라도, 한쪽만 예외 객체·한쪽만 메시지로 관리하면 추후 파싱/로깅 로직이 이원화됩니다.
가능하다면FooterState.Error도 예외 객체를 받아 내부에서 메시지를 파생하도록 통일해보세요.core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/ErrorDialogSpec.kt (1)
3-7: action 기본값 추가 제안대부분의 에러 다이얼로그는 별도 액션 없이 닫기만 합니다.
action: () -> Unit = {}로 기본값을 주면 호출부에서 람다 생략이 가능해 코드가 간결해집니다.core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedDialog.kt (1)
66-73: 불필요한 변수 참조
title?.let { … Text(text = title, …) }구문에서 람다 내부는it를 바로 쓰는 편이 명확합니다.- title?.let { - Text( - text = title, + title?.let { + Text( + text = it,feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt (2)
59-69: 전역 에러 이벤트 연속 수신 시 덮어쓰기 가능성다이얼로그가 떠있는 동안 새 이벤트가 오면 기존 스펙을 덮어씁니다. UX 상 혼란이 있을 수 있으니 큐잉 또는 표시 중엔 드롭 등 정책을 정해 주세요. 간단히 드롭하려면 아래처럼 가드 가능합니다.
LaunchedEffect(Unit) { ErrorEventHelper.errorEvent.collect { event -> - when (event) { + if (dialogSpec.value != null) return@collect + when (event) { is ErrorEvent.ShowDialog -> { dialogSpec.value = event.spec } } } }필요 시 SnapshotStateList로 큐를 두고, confirm/dismiss 시 다음 항목을 표시하는 방식도 고려해 보세요.
57-57: 설정 변경 시 다이얼로그 유지가 필요하다면 saveable 고려dialogSpec는 remember 상태라 회전 등 구성 변경 시 사라집니다. 유지가 필요하면 Saver를 제공해 rememberSaveable로 전환을 검토해 주세요(단, ErrorDialogSpec이 직렬화 가능해야 합니다).
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (2)
23-31: 재사용성 향상을 위한 modifier 파라미터 추가 제안항상 fillMaxSize로 고정되어 있어 섹션 단위(부분 영역) 에러 UI 재사용이 어렵습니다. modifier를 받아 확장 가능하게 해주세요.
-@Composable -fun ReedErrorUi( - exception: Throwable, - onRetryClick: () -> Unit, -) { +@Composable +fun ReedErrorUi( + exception: Throwable, + onRetryClick: () -> Unit, + modifier: Modifier = Modifier, +) { - val message = if (exception.isNetworkError()) stringResource(R.string.network_error_message) else stringResource(R.string.server_error_message) - Box( - modifier = Modifier.fillMaxSize(), + val message = + if (exception.isNetworkError()) + stringResource(R.string.network_error_message) + else + stringResource(R.string.server_error_message) + Box( + modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center, ) {
27-27: 가독성: 메시지 분기 한 줄 표기 → 여러 줄로 정리(선택)조건식이 길어 가독성이 떨어집니다. when/여러 줄 if로 정리하면 읽기 쉬워집니다.
- val message = if (exception.isNetworkError()) stringResource(R.string.network_error_message) else stringResource(R.string.server_error_message) + val message = if (exception.isNetworkError()) { + stringResource(R.string.network_error_message) + } else { + stringResource(R.string.server_error_message) + }core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt (1)
65-93: 하드코딩된 오류 메시지를 문자열 리소스로 분리 권장
buildDialog내부의 한국어 메시지가 코드에 직접 하드코딩돼 있습니다.
향후 다국어 지원 및 유지보수를 위해core/common/res/values/strings.xml등의 리소스로 옮겨 주세요.
이 변경은 UI 모듈의 문자열 관리 방식과도 일관성을 맞추는 데 도움이 됩니다.feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt (4)
136-145: Idle 상태에서 빈 화면 대신 Loading과 통합해 UX 플리커를 줄여주세요.초기 진입 시 Idle → 즉시 Loading 전환되는 동안 빈 화면이 잠깐 보일 수 있습니다. Idle과 Loading을 묶어 동일한 로딩 UI를 보여주는 편이 자연스럽습니다.
- when (state.uiState) { - is UiState.Idle -> {} - is UiState.Loading -> { + when (state.uiState) { + is UiState.Idle, + is UiState.Loading -> { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center, ) { CircularProgressIndicator(color = ReedTheme.colors.contentBrand) } }
187-192: 접근성: 장식 아이콘은 contentDescription을 null로 설정해당 아이콘은 드롭다운 표시용 장식 요소로 보입니다. 버튼 텍스트가 의미를 전달하므로 스크린리더에 노출하지 않도록
contentDescription = null을 권장합니다.- Icon( - imageVector = ImageVector.vectorResource(designR.drawable.ic_chevron_down), - contentDescription = "Dropdown Icon", + Icon( + imageVector = ImageVector.vectorResource(designR.drawable.ic_chevron_down), + contentDescription = null, modifier = Modifier.size(22.dp), tint = ReedTheme.colors.contentPrimary, )
253-273: Lazy 리스트는 items(list) 변형을 사용하면 더 간결하고 안전합니다인덱스 접근 기반
items(count)대신items(items, key)가 가독성/안정성(리스트 변경 시 OOB 방지)에 유리합니다.- } else { - items( - count = state.readingRecords.size, - key = { index -> state.readingRecords[index].id }, - ) { index -> - val record = state.readingRecords[index] + } else { + items( + items = state.readingRecords, + key = { record -> record.id }, + ) { record -> RecordItem( quote = record.quote, emotionTags = record.emotionTags.toImmutableList(), pageNumber = record.pageNumber, createdAt = record.createdAt.toFormattedDate(), modifier = Modifier .padding( start = ReedTheme.spacing.spacing5, end = ReedTheme.spacing.spacing5, bottom = ReedTheme.spacing.spacing3, ) .clickable { - state.eventSink(BookDetailUiEvent.OnRecordItemClick(record.id)) + state.eventSink(BookDetailUiEvent.OnRecordItemClick(record.id)) }, ) }
301-303: Preview 데이터 충분성 검증 요청
UiState.Success만 지정되어 있는데,BookDetailUiState의 다른 필드(예: bookDetail, readingRecords, seedsStats 등)가 non-null/기본값을 요구한다면 프리뷰가 깨질 수 있습니다. 팀에서 더미 데이터를 선호하므로, preview 전용 팩토리/스텁(state.preview())를 제공하거나 프리뷰 내에서 최소 더미를 세팅하는 것을 권장합니다.원하시면 preview용 스텁 생성/적용 패치를 작성해 드리겠습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (32)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/ErrorDialogSpec.kt(1 hunks)core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/ErrorScope.kt(1 hunks)core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/ErrorEventHelper.kt(1 hunks)core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt(3 hunks)core/ui/build.gradle.kts(1 hunks)core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedDialog.kt(2 hunks)core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt(1 hunks)core/ui/src/main/res/values/strings.xml(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt(7 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt(5 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt(2 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt(3 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt(5 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt(2 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.kt(3 hunks)feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt(2 hunks)feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt(2 hunks)feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUiState.kt(1 hunks)feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt(3 hunks)feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.kt(2 hunks)feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt(1 hunks)feature/library/src/main/res/values/strings.xml(0 hunks)feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt(2 hunks)feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt(3 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt(3 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt(2 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUiState.kt(1 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt(1 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt(2 hunks)feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUiState.kt(1 hunks)feature/search/src/main/res/values/strings.xml(0 hunks)
💤 Files with no reviewable changes (2)
- feature/library/src/main/res/values/strings.xml
- feature/search/src/main/res/values/strings.xml
🧰 Additional context used
🧠 Learnings (18)
📓 Common learnings
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#75
File: feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt:0-0
Timestamp: 2025-07-29T06:07:11.727Z
Learning: seoyoon513 팀에서는 UI 구현 단계에서 더미 데이터를 하드코딩하여 화면을 먼저 구현하고, 이후 서버 연동 시점에 실제 데이터로 교체하는 개발 방식을 사용합니다.
📚 Learning: 2025-07-28T18:22:00.618Z
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#72
File: feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt:70-70
Timestamp: 2025-07-28T18:22:00.618Z
Learning: LiveTextAnalyzer는 ML Kit 에러를 addOnFailureListener로 처리하지만 로그만 남기고 presenter에 실패를 알리지 않아서, UI에서 텍스트 인식 실패 상태를 표시할 수 없는 문제가 있음. StillTextAnalyzer처럼 실패 콜백을 추가하는 것이 더 나은 해결책임
Applied to files:
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt
📚 Learning: 2025-07-31T16:58:59.404Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트는 현재 다국어 지원 계획이 없어서 모델에 한글 문자열을 직접 포함하는 것이 허용된다.
Applied to files:
core/ui/src/main/res/values/strings.xml
📚 Learning: 2025-07-31T16:58:59.404Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트에서 core:model 모듈은 순수 Kotlin 모듈이므로 Android 리소스(R.string 등)에 접근할 수 없다.
Applied to files:
core/ui/src/main/res/values/strings.xmlfeature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.ktfeature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.ktcore/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
📚 Learning: 2025-07-31T23:17:40.054Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.054Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.ktfeature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.ktfeature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.ktfeature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktcore/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
📚 Learning: 2025-07-20T12:34:23.786Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#61
File: feature/webview/build.gradle.kts:17-21
Timestamp: 2025-07-20T12:34:23.786Z
Learning: Reed-Android 프로젝트에서는 `booket.android.feature` convention plugin을 사용하여 feature 모듈들의 공통 의존성을 관리한다. 이 plugin은 Circuit, Compose, 그리고 core 모듈들의 의존성을 자동으로 포함하므로, 각 feature 모듈의 build.gradle.kts에서는 특별한 의존성(예: libs.logger, libs.kakao.auth)만 별도로 선언하면 된다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.ktfeature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.ktcore/ui/build.gradle.ktsfeature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.ktcore/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
📚 Learning: 2025-07-31T23:22:02.816Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt:25-59
Timestamp: 2025-07-31T23:22:02.816Z
Learning: BookDetailScreen의 RecordsCollection 컴포넌트는 상위 Column의 verticalScroll과 LazyColumn의 무한 스크롤을 동시에 지원해야 하는 중첩 스크롤 시나리오입니다. LazyColumn에 고정 높이를 설정하지 않으면 앱이 충돌하는 이슈가 있어서, 현재는 높이를 계산하여 설정하고 userScrollEnabled = false로 구현되어 있습니다. 향후 InfiniteLazyColumn과 nestedScrollConnection을 도입하여 전체 화면 스크롤 + LazyColumn 무한 스크롤을 지원할 예정입니다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
📚 Learning: 2025-07-09T01:14:29.836Z
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.
Applied to files:
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.ktfeature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.ktfeature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.ktfeature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.ktcore/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.ktcore/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedDialog.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
📚 Learning: 2025-07-28T18:08:47.298Z
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#72
File: feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/component/CameraFrame.kt:21-82
Timestamp: 2025-07-28T18:08:47.298Z
Learning: Jetpack Compose에서 scale() 변환은 시각적 변환만 적용하며 레이아웃 좌표계는 변경하지 않는다. 따라서 scale(scaleX = -1f, scaleY = -1f)로 반전된 아이콘에서 padding()은 원래 레이아웃 기준으로 동작하므로, 시각적으로 올바른 위치를 위해서는 변환 전 좌표계 기준으로 padding을 설정해야 한다.
Applied to files:
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.ktfeature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt
📚 Learning: 2025-07-12T01:33:57.101Z
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#45
File: core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/appbar/ReedTopAppBar.kt:65-65
Timestamp: 2025-07-12T01:33:57.101Z
Learning: Reed Android 프로젝트에서 타이포그래피 사용 규칙: 톱 앱바(Top App Bar)에서는 `headline2SemiBold`를 사용하고, 바텀시트(Bottom Sheet)에서는 `heading2SemiBold`를 사용한다. 이는 의도적인 디자인 시스템 차별화이다.
Applied to files:
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.ktfeature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
📚 Learning: 2025-07-29T07:02:18.885Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#77
File: feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt:73-73
Timestamp: 2025-07-29T07:02:18.885Z
Learning: Kotlin에서 같은 패키지 내의 파일들은 패키지 레벨에 정의된 const val 상수를 import 없이 직접 접근할 수 있습니다. OnboardingPresenter.kt와 OnboardingUi.kt 모두 com.ninecraft.booket.feature.onboarding 패키지에 속해 있어서 ONBOARDING_STEPS_COUNT 상수를 공유할 수 있습니다.
Applied to files:
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.ktfeature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.ktfeature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.ktfeature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
📚 Learning: 2025-07-16T15:54:19.322Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegatingNavigator.kt:0-0
Timestamp: 2025-07-16T15:54:19.322Z
Learning: In the Reed-Android project using Circuit architecture, DelegatingNavigator was restored because NavigableCircuitContent can only inject a single navigator, but the dual-navigator architecture (childNavigator for bottom navigation screens, rootNavigator for full-screen screens) requires a delegating component to work within Circuit's constraint.
Applied to files:
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.ktfeature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.ktfeature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
📚 Learning: 2025-07-31T23:30:37.547Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt:18-47
Timestamp: 2025-07-31T23:30:37.547Z
Learning: In Circuit architecture, presenters receive the Screen object directly as a constructor parameter (e.g., Assisted private val screen: RecordDetailScreen), and screen parameters are accessed through this screen object (e.g., screen.recordId). Screen parameters should not be added as separate constructor parameters.
Applied to files:
feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.ktfeature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
📚 Learning: 2025-07-16T16:11:27.044Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegateNavigator.kt:0-0
Timestamp: 2025-07-16T16:11:27.044Z
Learning: In the Reed-Android project's DelegateNavigator implementation, the pop(), peek(), and peekBackStack() methods should always use childNavigator without branching logic, as they operate on the currently active navigation stack. Only goTo() and resetRoot() methods need to route between childNavigator and rootNavigator based on screen type.
Applied to files:
feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt
📚 Learning: 2025-07-16T16:11:27.044Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegateNavigator.kt:0-0
Timestamp: 2025-07-16T16:11:27.044Z
Learning: In the Reed-Android project's DelegateNavigator implementation, the pop(), peek(), and peekBackStack() methods should always use childNavigator without branching logic, as they operate on the currently active navigation stack. Only goTo() and resetRoot() methods need to route between childNavigator and rootNavigator based on screen type using isMainTabScreen() function.
Applied to files:
feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt
📚 Learning: 2025-07-08T12:33:01.863Z
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#32
File: core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/button/ButtonColorStyle.kt:10-16
Timestamp: 2025-07-08T12:33:01.863Z
Learning: Reed Android 프로젝트에서 KAKAO 버튼 스타일은 디자이너가 pressed 상태 색상을 별도로 정의하지 않았기 때문에 pressed 상태에서도 동일한 Kakao 색상을 사용한다.
Applied to files:
feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt
📚 Learning: 2025-07-22T05:19:10.071Z
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#63
File: feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt:28-53
Timestamp: 2025-07-22T05:19:10.071Z
Learning: feature/library의 LibraryPresenter에서 현재 FilterChipState의 카운트 값들은 UI 확인용 더미 데이터이며, API가 준비되면 실제 데이터로 교체될 예정입니다.
Applied to files:
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt
📚 Learning: 2025-07-14T00:51:38.952Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:33-44
Timestamp: 2025-07-14T00:51:38.952Z
Learning: The project uses Kotlinx Serialization with JSON configuration that includes `ignoreUnknownKeys = true`, `encodeDefaults = true`, `isLenient = true`, and `prettyPrint = true` to handle API response parsing robustly, making additional null safety measures less critical if the API consistently provides all expected fields.
Applied to files:
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt
🧬 Code Graph Analysis (11)
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt (1)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt (1)
postErrorDialog(46-58)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.kt (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt (1)
getEmotionImageResourceByDisplayName(92-100)
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt (1)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (1)
ReedErrorUi(22-48)
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.kt (1)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (1)
ReedErrorUi(22-48)
feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt (1)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedDialog.kt (1)
ReedDialog(25-111)
feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt (1)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (1)
ReedErrorUi(22-48)
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt (2)
core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/BookRepository.kt (1)
filterLibraryBooks(29-33)core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultBookRepository.kt (1)
filterLibraryBooks(45-47)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (1)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt (1)
postErrorDialog(46-58)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (1)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/button/ReedButton.kt (1)
ReedButton(34-114)
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt (1)
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (1)
ReedErrorUi(22-48)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt (5)
core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/NetworkImage.kt (1)
NetworkImage(17-39)core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/ReedDivider.kt (1)
ReedDivider(13-21)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/QuoteBox.kt (1)
QuoteBox(16-49)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.kt (1)
ReviewBox(26-78)core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (1)
ReedErrorUi(22-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-build
🔇 Additional comments (29)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/ErrorScope.kt (1)
3-5: 스코프 정의 방향 좋습니다에러 범주가 명확해져 전역/국소 처리 분리가 쉬워집니다.
core/ui/src/main/res/values/strings.xml (1)
5-6: 에러 메시지 리소스 추가 적절합니다네트워크/서버 에러를 구분한 2줄 메시지 구성은 UI 재사용에 유용합니다.
core/ui/build.gradle.kts (1)
15-15: core.ui → core.common 의존성 추가 검증 완료 (순환 의존 없음 확인)
의존성 추가 방향이 타당합니다. 제공해주신 스크립트로 확인한 결과 core.common에서 core.ui를 참조하는 부분이 없어 순환 의존이 없습니다.feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUiState.kt (1)
16-16: UiState.Error에 Throwable 보존: 정보 전달력 향상예외를 그대로 전달해 에러 UI에서 맥락별 처리(네트워크/서버 등)가 쉬워집니다. 다만 화면에 exception.message를 직접 노출하지 않도록 유지 부탁드립니다(ReedErrorUi로 매핑).
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt (1)
17-17: 라이브러리도 Throwable 보존으로 통일: 일관성 확보홈/검색/상세와 동일한 패턴으로 통일된 점 👍. 에러 UI 재사용성이 높습니다.
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUiState.kt (1)
19-20: UiState.Error에 Throwable 전달 일관성 확인 완료전체 코드베이스에서
UiState.Error(...)호출부를 점검한 결과, 모두Throwable(exception, e 등)을 인자로 넘기고 있으며 과거에 문자열(message)을 직접 넘기던 호출은 없습니다. 마이그레이션 누락 사항이 없으므로 추가 조치가 필요 없습니다.feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUiState.kt (1)
16-17: 검증 완료: 호출부 마이그레이션 일관성 확인됨라이브러리 검색과 도서 검색 모두
UiState.Error(exception)형태로 예외 기반 에러 상태가 통일되어 있으며,
관련 호출부 마이그레이션이 누락 없이 적용되었습니다.
- feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt:85
- feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt:95
feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt (1)
170-175: ReedErrorUi 도입으로 에러 UI 일원화 완료
state.uiState.exception을 전달하여 네트워크/서버 에러 메시지 분기 및 재시도 콜백을 통합 처리한 점 좋습니다.- 하단 탭바(MainBottomBar) 위 영역만 에러 UI로 채워져 UX 일관성 유지가 됩니다.
(확인 사항) Presenter의
OnRetryClick가 초기 홈 데이터 재로딩으로 정확히 이어지는지 한번 더 점검 부탁드립니다.Also applies to: 30-30
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt (2)
12-15: 전역 에러 다이얼로그 도입을 위한 import 추가, 방향성 적합합니다.새 전역 에러 처리 플로우(ErrorScope + postErrorDialog) 적용을 위한 준비로 보이며 일관성 있는 설계입니다.
95-95: 초기 로딩 실패 시 예외 객체 전달로 전환한 점 LGTMUiState.Error에 Throwable을 담아 ReedErrorUi에서 타입 기반 메시지 분기가 가능해졌습니다. 다른 모듈과의 일관성도 확보됩니다.
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt (2)
29-29: 공통 에러 UI 컴포넌트 도입 import, 잘 적용되었습니다.모듈 전반에서 에러 UI를 통일하는 방향과 일치합니다.
107-110: ReedErrorUi로 에러 UI 일원화, 적합합니다.예외를 그대로 전달해 네트워크/서버 에러 메시지를 표준화하고, Retry 이벤트 연결도 명확합니다.
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt (1)
85-85: 초기 페이지 실패 시 예외 객체 전달로 일관성 확보, LGTM풀스크린 에러 UI(ReedErrorUi)와 맞물려 일관된 에러 표기가 가능합니다. 페이징 실패 시 FooterState.Error는 문자열 유지로 충분합니다.
feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt (1)
52-52: 예외 객체 기반 에러 상태 전환, 일관성 좋습니다ReedErrorUi와의 결합을 고려한 합리적 변경입니다.
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.kt (2)
26-26: 공통 에러 UI 컴포넌트 import, 적절합니다모듈 간 에러 UI 일관성 확보에 기여합니다.
150-153: ReedErrorUi 적용으로 에러 UI 일관화, 잘 반영되었습니다예외를 그대로 전달하여 메시지 표준화 및 재시도 핸들링이 단순해졌습니다.
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt (2)
116-123: 중복 호출 방지 가드 👍현재 필터와 동일한 옵션일 때 조기 return 을 추가해 불필요한 API 호출을 막았습니다. 성능·트래픽 모두 이득입니다.
154-160: 초기 로드 호출 위치 OK
LaunchedEffect(Unit)내에서 조건 없이 초기 데이터를 불러오도록 단순화한 변경은 논리적으로 문제없어 보입니다.
재합성에도 재호출되지 않으니(side-effect key가 Unit) 안심하셔도 됩니다.feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (1)
120-129: 다이얼로그·토스트 이중 알림 여부 확인 요청
postErrorDialog로 전역 다이얼로그를 띄운 뒤, 곧바로handleException내에서 토스트도 표시합니다.
동일 오류에 대해 다이얼로그 + 토스트가 연달아 노출되면 사용자 경험이 과도하게 시끄러울 수 있습니다.
두 가지 알림이 모두 필요한지 한 번 더 점검해주세요.core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedDialog.kt (1)
30-30: title nullable 처리 👍타이틀을 선택적으로 받도록 변경해 다이얼로그 재사용성이 좋아졌습니다.
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt (2)
20-21: 예외 객체 전달로 정보 보존 👍에러 상태가 메시지 문자열 대신
Throwable을 보유하도록 변경되어 스택트레이스 등 상세 정보를 활용할 수 있게 됐습니다.
66-67: 재시도 이벤트 추가 확인
OnRetryClick이벤트가 정의되었습니다. 프레젠터에서 해당 이벤트를 처리하고 있는지, UI에서 적절히 호출되는지 확인만 부탁드립니다.feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt (2)
34-34: ReedErrorUi 도입으로 에러 UI 일관화 👍공통 에러 컴포넌트로의 전환이 적절합니다. 모듈 간 일관성 측면에서 좋습니다.
127-130: 에러 상태 렌더링을 ReedErrorUi로 교체 — 올바른 사용Throwable 기반의 예외 전달과 재시도 콜백 연결이 명확합니다. 화면 최초 로딩 실패 시 풀스크린 에러로의 전환 정책에도 부합합니다.
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/ReedErrorUi.kt (1)
22-48: 컴포넌트 구현 전반 LGTM네트워크/서버 예외 분기와 공통 테마 적용, 재시도 버튼 연결까지 목적에 부합합니다. 미리보기까지 포함되어 있어 디자인 확인도 수월합니다.
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt (2)
9-14: UiState 정의 방향 적절Idle/Loading/Success/Error(예외 포함) 구성이 명확합니다. 에러 UI 및 재시도 처리와 잘 맞습니다.
17-17: RecordDetailUiState에 uiState 추가 LGTM기존 상태와 충돌 없고, 기본값 Idle 설정도 적절합니다.
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt (2)
153-156: loadMore 중복 호출 가드 확인 요청InfinityLazyColumn에서 스크롤 바닥 진입 시
OnLoadMore가 빠르게 연속 호출될 수 있습니다. 컴포넌트 내부에서 디바운스/스로틀이 없거나, footerState 로딩 중 재호출을 막지 않는다면 presenter 레벨에서 in-flight 플래그로 중복 요청을 방지해 주세요.
285-290: ReedErrorUi 연동 및 재시도 콜백 전달 훌륭합니다예외 기반 에러 모델과 재시도 이벤트가 잘 연결되어 있습니다. 전역/로컬 에러 UX 방향과도 일치합니다.
| Image( | ||
| painter = painterResource(getEmotionImageResourceByDisplayName(emotion)), | ||
| contentDescription = "Emotion Graphic", | ||
| modifier = Modifier | ||
| .size(ReedTheme.spacing.spacing10) | ||
| .background( | ||
| color = ReedTheme.colors.bgTertiary, | ||
| shape = CircleShape, | ||
| ) | ||
| .clip(shape = CircleShape), | ||
| .clip(CircleShape), | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
감정 이미지 표시 개선은 적절하나, 접근성(i18n) 및 안전성 보완 필요
- contentDescription가 하드코딩되어 있어 현지화가 어렵습니다. stringResource로 교체해 주세요.
getEmotionImageResourceByDisplayName(emotion)는 표시 문자열 의존이라 로케일/카피 변경에 취약합니다. 가능하면 안정 키(코드/enum) 기반 매핑으로 전환을 권장합니다.
예시:
- Image(
- painter = painterResource(getEmotionImageResourceByDisplayName(emotion)),
- contentDescription = "Emotion Graphic",
- modifier = Modifier
- .size(ReedTheme.spacing.spacing10)
- .clip(CircleShape),
- )
+ Image(
+ painter = painterResource(getEmotionImageResourceByDisplayName(emotion)),
+ contentDescription = stringResource(R.string.cd_emotion_graphic),
+ modifier = Modifier
+ .size(ReedTheme.spacing.spacing10)
+ .clip(CircleShape),
+ )🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/component/ReviewBox.kt
around lines 50 to 56, replace the hardcoded contentDescription string with a
localized string using stringResource for accessibility and i18n support.
Additionally, refactor the image resource retrieval to use a stable key such as
an enum or code instead of relying on display name strings, to improve
robustness against locale or copy changes.
| fun getRecordDetail(readingRecordId: String) { | ||
| scope.launch { | ||
| uiState = UiState.Loading | ||
|
|
||
| repository.getRecordDetail(readingRecordId = readingRecordId) | ||
| .onSuccess { result -> | ||
| uiState = UiState.Success | ||
| recordDetailInfo = result | ||
| } | ||
| .onFailure { exception -> | ||
| uiState = UiState.Error(exception) | ||
| val handleErrorMessage = { message: String -> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
중복 요청/경쟁 상태 가능성 — 이전 fetch 취소 로직 추가 권장
연속 재시도 시 이전 Job이 뒤늦게 완료되면 최신 결과를 덮어쓸 수 있습니다. 이전 작업 취소 후 새 작업을 시작하세요.
- fun getRecordDetail(readingRecordId: String) {
- scope.launch {
+ fun getRecordDetail(readingRecordId: String) {
+ // 이전 요청 취소 후 새 요청 시작
+ fetchJob?.cancel()
+ fetchJob = scope.launch {
uiState = UiState.Loading
repository.getRecordDetail(readingRecordId = readingRecordId)
.onSuccess { result ->
uiState = UiState.Success
recordDetailInfo = result
}
.onFailure { exception ->
uiState = UiState.Error(exception)
val handleErrorMessage = { message: String ->
Logger.e(message)
sideEffect = RecordDetailSideEffect.ShowToast(message)
}위 변경에 필요한 보조 코드(파일 상단부에 추가):
// 추가 import
import kotlinx.coroutines.Job
// present() 내부 상태 정의 근처
var fetchJob by rememberRetained { mutableStateOf<Job?>(null) }🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
around lines 38 to 49, the getRecordDetail function launches a coroutine without
cancelling any previous ongoing fetch, which can cause outdated results to
overwrite newer ones. To fix this, declare a Job variable to keep track of the
current fetch coroutine, cancel the existing fetchJob if it is active before
launching a new coroutine, and assign the new coroutine's Job to fetchJob. Also,
add the necessary import for kotlinx.coroutines.Job and define the fetchJob
state variable near the present() function's state declarations as shown in the
comment.
| data object Loading : UiState | ||
| data object Success : UiState | ||
| data object Error : UiState | ||
| data class Error(val exception: Throwable) : UiState |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
예외 메시지 직접 노출 여부 점검 스크립트
혹시 남아있는 raw message 노출이 있는지 점검해 주세요.
🏁 Script executed:
#!/bin/bash
# exception.message 직접 노출 탐지
rg -n "exception\\.message|Throwable\\.message"Length of output: 2366
사용자에게 예외 메시지(raw message) 직접 노출 지점 확인 및 처리 필요
스크립트 실행 결과, 아래 파일들에서 exception.message 또는 Throwable.message 를 직접 호출하여 UI나 로그에 노출하고 있습니다. 사용자에게는 보다 친화적인 메시지를 보여주어야 하므로, 중앙화된 예외 처리 로직(core/common/utils/HandleException.kt)을 활용하거나 커스텀 메시지 변환 함수를 만들어 아래 위치를 우선 수정해 주세요.
- feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt:83
- feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt:93
- feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt:92
- feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt:173
- core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/analyzer/StillTextAnalyzer.kt:58
- core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/analyzer/LiveTextAnalyzer.kt:55, 64
- feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt:46, 47, 74, 75
- feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt:75, 76
- core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt:39
이 부분들을 exception.message ?: defaultMessage 형태가 아닌, HandleException.getUserFriendlyMessage(exception) 등으로 대체하여 예외 메시지 노출을 방지해 주세요.
🤖 Prompt for AI Agents
In feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUiState.kt
at line 16, the Error data class exposes the raw exception message directly. To
prevent showing raw exception messages to users, replace any direct usage of
exception.message with a call to a centralized exception handler like
HandleException.getUserFriendlyMessage(exception) or a custom message conversion
function. This ensures user-friendly error messages are displayed consistently
across the app.
| data object Loading : UiState | ||
| data object Success : UiState | ||
| data class Error(val message: String) : UiState | ||
| data class Error(val exception: Throwable) : UiState |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
예외 메시지 직접 노출 점검
라이브러리 화면에서도 raw exception.message 노출이 없는지 확인 부탁드립니다.
🏁 Script executed:
#!/bin/bash
# Library 모듈 내 message 노출 탐지
rg -n "exception\\.message|Throwable\\.message" feature/libraryLength of output: 240
예외 메시지 직접 노출 방지 필요
UI에 exception.message를 그대로 노출하고 있어 민감 정보 유출 및 보안‧사용자 경험 이슈가 발생할 수 있습니다. 사용자에게는 내부 예외 메시지 대신 친숙한 오류 메시지를 제공하도록 수정해주세요.
- feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt:92
현재 코드:➔val errorMessage = exception.message ?: "알 수 없는 오류가 발생했습니다."
exception.message대신 공통 에러 핸들러나 커스텀 매핑 함수(e.g.getUserFriendlyError(exception))를 사용해 사용자 친화적인 메시지를 리턴하도록 변경 바랍니다.
🤖 Prompt for AI Agents
In
feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt
at line 17, the Error data class exposes the raw exception which leads to
displaying sensitive exception messages directly in the UI. To fix this, avoid
using exception.message directly for user-facing messages. Instead, implement or
use a common error handler or a custom mapping function like
getUserFriendlyError(exception) to convert exceptions into user-friendly error
messages before passing them to the UI.
| postErrorDialog( | ||
| errorScope = ErrorScope.BOOK_REGISTER, | ||
| exception = exception, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
에러 다이얼로그에 재시도 action을 연결하세요 + 중복 피드백(다이얼로그+토스트) 방지 권장
현재 다이얼로그는 action 없이 표시됩니다. 실패 시 재시도 버튼이 있는 사양이라면 기존 파라미터로 재시도 가능하도록 action을 전달해 UX를 완성하는 것을 권장합니다. 또한 바로 아래에서 토스트/handleException으로 별도 피드백을 중복 노출하고 있어 사용자 경험이 지저분해질 수 있습니다. BOOK_REGISTER 스코프는 다이얼로그 우선으로 정리하는 편이 명확합니다.
다이얼로그에 재시도 action 연결(이 변경 범위 내에서 적용 가능):
postErrorDialog(
errorScope = ErrorScope.BOOK_REGISTER,
exception = exception,
+ action = { upsertBook(isbn13, bookStatus) },
)다이얼로그와 토스트의 중복을 피하려면, 아래 블록을 상황에 따라 제거하거나(권장) 네트워크 외 에러에만 토스트를 보이도록 분기하세요(선택):
// 옵션 A(권장): BOOK_REGISTER 실패는 다이얼로그만 사용
// handleException(...) 호출 제거
// 옵션 B(선택): 네트워크 외 에러에만 토스트
if (!exception.isNetworkError()) {
handleException(
exception = exception,
onError = handleErrorMessage,
onLoginRequired = { navigator.resetRoot(LoginScreen) },
)
}🤖 Prompt for AI Agents
In
feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt
around lines 120 to 123, the postErrorDialog call lacks a retry action, and
there is redundant user feedback due to both dialog and toast being shown. Fix
this by adding a retry action parameter to postErrorDialog to enable retry
functionality. Then, remove the handleException call below to avoid duplicate
feedback, or alternatively, conditionally show the toast only for non-network
errors by wrapping handleException in a network error check.
헤이딜러나 Soop과 같은 앱의 경우엔 이 경우 스플래시 화면에서 다이얼로그로 안내 후 앱을 종료하는 전략을 세우고 있더군여. 아무래도 앱을 시작할때 저희는 아직 없지만 서버에 사용자 정보를 보내는 경우가 있는데(fcm 토큰을 보낸다던지), 이거 부터 실패하면 이후 앱을 사용하는데 문제가 있으므로 앱을 종료시키더군여. 저도 이 플로우에 대해선 어느정도 동의하는 부분입니다. |
이 부분은 토스트만으로 해도 될 것 같은... |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (1)
97-101: 첫 페이지 로드 후isLastPage·currentStartIndex갱신 누락
초기 로드가 완료돼도 두 변수가 초기값(0/false)에 머물러 PAGE_SIZE 미만 데이터일 때 추가 로드를 계속 시도합니다.
지난 리뷰에서도 동일 지적이 있었으므로 반드시 반영해 주세요.bookDetail = detail seedsStates = seeds.categories.toImmutableList() readingRecords = records.content.toPersistentList() currentStartIndex = START_INDEX isLastPage = records.content.size < PAGE_SIZE
🧹 Nitpick comments (2)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt (2)
135-144: 로딩 인디케이터 접근성 개선 필요
CircularProgressIndicator에contentDescription이 없어 스크린리더 사용 시 로딩 상태를 인지할 수 없습니다.
Modifier.semantics { contentDescription = ... }또는progressSemantics()를 적용해 주세요.
284-289: Error UI 크기 지정 누락
ReedErrorUi가Modifier.fillMaxSize()없이 배치되면 상위 레이아웃에 따라 중앙 정렬이 깨질 수 있습니다.
modifier = Modifier.fillMaxSize()를 넘겨 전체 화면을 일관되게 덮도록 하는 편이 안전합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/ErrorEventHelper.kt(1 hunks)core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt(3 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt(7 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt(4 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt(5 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt(2 hunks)feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/ErrorEventHelper.kt
🚧 Files skipped from review as they are similar to previous changes (4)
- feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt
- feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUiState.kt
- core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt
- feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#75
File: feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt:0-0
Timestamp: 2025-07-29T06:07:11.727Z
Learning: seoyoon513 팀에서는 UI 구현 단계에서 더미 데이터를 하드코딩하여 화면을 먼저 구현하고, 이후 서버 연동 시점에 실제 데이터로 교체하는 개발 방식을 사용합니다.
📚 Learning: 2025-07-20T12:34:23.786Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#61
File: feature/webview/build.gradle.kts:17-21
Timestamp: 2025-07-20T12:34:23.786Z
Learning: Reed-Android 프로젝트에서는 `booket.android.feature` convention plugin을 사용하여 feature 모듈들의 공통 의존성을 관리한다. 이 plugin은 Circuit, Compose, 그리고 core 모듈들의 의존성을 자동으로 포함하므로, 각 feature 모듈의 build.gradle.kts에서는 특별한 의존성(예: libs.logger, libs.kakao.auth)만 별도로 선언하면 된다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
📚 Learning: 2025-07-31T23:22:02.816Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt:25-59
Timestamp: 2025-07-31T23:22:02.816Z
Learning: BookDetailScreen의 RecordsCollection 컴포넌트는 상위 Column의 verticalScroll과 LazyColumn의 무한 스크롤을 동시에 지원해야 하는 중첩 스크롤 시나리오입니다. LazyColumn에 고정 높이를 설정하지 않으면 앱이 충돌하는 이슈가 있어서, 현재는 높이를 계산하여 설정하고 userScrollEnabled = false로 구현되어 있습니다. 향후 InfiniteLazyColumn과 nestedScrollConnection을 도입하여 전체 화면 스크롤 + LazyColumn 무한 스크롤을 지원할 예정입니다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.ktfeature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
📚 Learning: 2025-07-29T07:02:18.885Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#77
File: feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt:73-73
Timestamp: 2025-07-29T07:02:18.885Z
Learning: Kotlin에서 같은 패키지 내의 파일들은 패키지 레벨에 정의된 const val 상수를 import 없이 직접 접근할 수 있습니다. OnboardingPresenter.kt와 OnboardingUi.kt 모두 com.ninecraft.booket.feature.onboarding 패키지에 속해 있어서 ONBOARDING_STEPS_COUNT 상수를 공유할 수 있습니다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
📚 Learning: 2025-07-16T15:54:19.322Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegatingNavigator.kt:0-0
Timestamp: 2025-07-16T15:54:19.322Z
Learning: In the Reed-Android project using Circuit architecture, DelegatingNavigator was restored because NavigableCircuitContent can only inject a single navigator, but the dual-navigator architecture (childNavigator for bottom navigation screens, rootNavigator for full-screen screens) requires a delegating component to work within Circuit's constraint.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
🧬 Code Graph Analysis (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (1)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt (2)
handleException(17-44)postErrorDialog(46-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-build
| is BookDetailUiEvent.OnRetryClick -> { | ||
| scope.launch { | ||
| initialLoad() | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Retry 시 페이징 상태 초기화 필요
OnRetryClick에서 initialLoad()만 호출하면 isLastPage, currentStartIndex, footerState 등이 이전 값 그대로 남습니다.
재시도 전에 해당 값을 초기화하지 않으면 잘못된 페이징 동작과 UI 상태가 발생할 수 있습니다.
🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
around lines 238 to 242, the retry handler calls initialLoad() without resetting
paging state variables like isLastPage, currentStartIndex, and footerState. To
fix this, add code to reset these paging-related variables to their initial
values before calling initialLoad() in the OnRetryClick event handler to ensure
correct paging behavior and UI state on retry.
현재 에러 다이얼로그 플로우는 아래와 같은데요,
토스트나 스낵바로도 충분하지 않을까..라는 생각도 있습니다 |
맞아여 버튼은 다시 누를수있으니까여 |
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (1)
241-244: 재시도 시 페이징 상태 초기화 누락 — 이전 리뷰와 동일
initialLoad()호출 전에footerState(특히 Error 상태) 를Idle로,
readingRecords,isLastPage,currentStartIndex등을 초기화하지 않으면
재시도 후에도 잘못된 페이징 UI 가 남을 수 있습니다.이 부분은 이전 커밋에서도 지적되었으므로 참고 바랍니다.
🧹 Nitpick comments (2)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (2)
82-95: 동시 요청 실패 시 다른 요청까지 취소될 수 있습니다
coroutineScope { async { … } }구조에서는 하나의 네트워크 호출이 예외를 던지면 나머지async작업도 전파된 취소로 중단됩니다.
각 요청을 독립적으로 시도하고, 일부만 실패해도 전체 UI 를 에러로 전환할지 선택하려면supervisorScope로 감싸는 편이 안전합니다.supervisorScope { val detailDef = async { … } val seedsDef = async { … } val recordsDef = async { … } // 필요에 따라 개별 await + 개별 에러 처리 }이렇게 하면 특정 호출 실패 시 다른 호출이 불필요하게 취소되는 것을 방지할 수 있습니다.
135-139: 다이얼로그와 토스트가 중복으로 표시됩니다
postErrorDialog()로 오류 다이얼로그를 띄운 뒤 바로 아래에서 동일 오류를ShowToast로도 알리고 있습니다.
사용자에게 동일 메시지가 두 번 노출되어 UX 가 과도할 수 있으니 하나만 남기고 통일하는 것을 권장드립니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/ErrorEventHelper.kt(1 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt(7 hunks)feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt(3 hunks)feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt(2 hunks)feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/ErrorEventHelper.kt
🚧 Files skipped from review as they are similar to previous changes (3)
- feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt
- feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt
- feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#75
File: feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt:0-0
Timestamp: 2025-07-29T06:07:11.727Z
Learning: seoyoon513 팀에서는 UI 구현 단계에서 더미 데이터를 하드코딩하여 화면을 먼저 구현하고, 이후 서버 연동 시점에 실제 데이터로 교체하는 개발 방식을 사용합니다.
📚 Learning: 2025-07-31T23:22:02.816Z
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordsCollection.kt:25-59
Timestamp: 2025-07-31T23:22:02.816Z
Learning: BookDetailScreen의 RecordsCollection 컴포넌트는 상위 Column의 verticalScroll과 LazyColumn의 무한 스크롤을 동시에 지원해야 하는 중첩 스크롤 시나리오입니다. LazyColumn에 고정 높이를 설정하지 않으면 앱이 충돌하는 이슈가 있어서, 현재는 높이를 계산하여 설정하고 userScrollEnabled = false로 구현되어 있습니다. 향후 InfiniteLazyColumn과 nestedScrollConnection을 도입하여 전체 화면 스크롤 + LazyColumn 무한 스크롤을 지원할 예정입니다.
Applied to files:
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
🧬 Code Graph Analysis (1)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (1)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt (2)
handleException(17-44)postErrorDialog(46-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-build
| }, | ||
| ) | ||
| try { | ||
| coroutineScope { |
There was a problem hiding this comment.
위에 정의한 rememberCoroutineScope 쓰면 되지않을까여
| uiState = UiState.Success | ||
| } | ||
| } catch (ce: CancellationException) { | ||
| throw ce |
There was a problem hiding this comment.
사실 ce를 presenter에서 던져도 CEH같은게 없다보니 던지는게 무의미하긴합니다.
| ) | ||
| try { | ||
| coroutineScope { | ||
| val bookDetailDef = async { bookRepository.getBookDetail(screen.isbn13).getOrThrow() } |
There was a problem hiding this comment.
아 Def가 Deferred 의 약자인가보군여
| getSeedsStats() | ||
| getBookDetail() | ||
| getReadingRecords() | ||
| initialLoad() |
There was a problem hiding this comment.
initialLoad로 함수 빼내는건 좋다고 생각하는데, 그러면 위와 같이 한 함수내애 async { } 로 모두 처리해줘야하는지라 좀 복잡하다고 생각해서 전 기존에 각각의 함수를 정의하고 scope.launch { }의 방식으로 구현을 했던건데요.(각각이 별도의 launch {} 내부에서 실행되기 때문에 3개의 함수는 병렬로 실행됨)
LaunchedEffect의 key로 retry flag 추가한다면 정의한다면, retry 상황에서 해당 flag만 true로 바꿔서 LaunchedEffect 내부 블럭을 재실행(재시도)시킬수도 있었을것같아여, 전 이 방식이 조금 더 낫다고 생각함다


🔗 관련 이슈
📙 작업 설명
ReedErrorUi로 화면별 에러 처리🧪 테스트 내역
📸 스크린샷 또는 시연 영상
✅ 전체 화면 에러 (화면에 데이터를 불러와 렌더링 해야하는 경우)
네트워크 에러 문구: 네트워크 연결이 불안정합니다. 인터넷 연결을 확인해주세요그 외 서버 에러 문구: 알 수 없는 문제가 발생했어요. 다시 시도해주세요✅ 에러 다이얼로그 (이미 화면을 보고 있는 상태에서 요청이 있는 경우)
네트워크 에러 문구: 네트워크 연결이 불안정합니다. 인터넷 연결을 확인해주세요그 외 서버 에러 문구: 상황별 상이함. ErrorScope로 Dialog message 분기(아래 코드 참고)💬 추가 설명 or 리뷰 포인트
📌 For PM
현재 에러 UI를 다이얼로그로만 보여주는 것에 대해,
1. 화면 렌더링 단계의 에러
이유:2. 화면 진입 이후 추가 데이터 요청 시 발생하는 에러 (로그인, 도서 등록, 기록 등록 등)
이유:3. 에러 메세지 가이드 개선
4. 2회 초과 요청 다이얼로그 분기 불필요
📌 For 지훈
Summary by CodeRabbit
신규 기능
버그 수정
UI/UX 개선
문서 및 리소스